# Copyright (c) 2009 Vitja Makarov <vitja.makarov@gmail.com>
# Licensed under the terms of the MIT License (see LICENSE.txt)

import warnings
from django.db import models
from django.db.models import signals
from django.utils.functional import curry
from django.core.urlresolvers import reverse
from django.utils.translation import get_language

from datetime import datetime

__all__ = ('translator', 'translate', 'update_translations')

class Translation(object):
    def __init__(self, lang):
        self.lang = lang
        self._strings = {}

    def __getitem__(self, string):
        return self._strings[unicode(string)]

    def append(self, trans):
        self._strings[trans.original] = trans

def create_translation_model(model):
    class Meta:
        app_label = model._meta.app_label
        unique_together = ('lang', 'obj')

    def get_value(instance, field_name):
        return getattr(instance, 'field_' + field_name)
    def set_value(instance, field_name, value):
        setattr(instance, 'field_' + field_name, value)

    attrs = {
             '__module__': model.__module__,
             'Meta': Meta,
             'lang': models.ForeignKey('translate.Language'),
             'obj': models.ForeignKey(model, related_name='translations'),
             'get_value': curry(get_value),
             'set_value': curry(set_value),
             '__unicode__': lambda self: u'%s: %s' % (self.lang.code, self.obj),
            }

    def get_translate_model(instance, lang=None):
        lang = translator.get_language(lang)
        return instance.Translate.model._default_manager.get_or_create(lang=lang, obj=instance)[0]
    model.get_translate_model = curry(get_translate_model)

    for field_name in model.Translate.fields:
        field = model._meta.get_field_by_name(field_name)[0]

        if isinstance(field, models.CharField):
            attrs['field_' + field_name] = models.CharField(max_length=255, null=True)
        elif isinstance(field, models.TextField):
            attrs['field_' + field_name] = models.TextField(null=True)

    return type('Localized%s' % model.__name__, (models.Model,), attrs)

class Translator(object):
    def __init__(self):
        self.models = []
        self.models_by_name = {}
        self.last_translation = None
        for model in models.get_models():
            self.add_model(model)
        self.languages = []
        self.by_lang = {}

    def get_model_name(self, model):
        return '%s/%s'  % (model._meta.app_label, model._meta.object_name.lower())

    def add_model(self, model):
        if not hasattr(model, 'Translate'):
            return
        translate = model.Translate
        if not hasattr(translate, 'fields'):
            warnings.warn("Model %s.%s has Translate but doesn't have fields"
                            % (model._meta.app_label, model._meta.object_name))
            return
        fields = []
        for i in model.Translate.fields:
            if not isinstance(model._meta.get_field_by_name(i)[0], (models.CharField, models.TextField)):
                warnings.warn("Model %s.%s: field %s is not CharField or TextField, will not be translated!"
                                % (model._meta.app_label, model._meta.object_name, i))
            else:
                setattr(model, 'localized_%s' % i, property(curry(self.localize_field, i)))
                fields.append(i)
        if len(fields) == 0:
            return
        model.Translate.fields = fields
        setattr(model, 'get_translate_url', curry(self.get_translate_url))
        self.models.append(model)
        self.models_by_name[self.get_model_name(model)] = model
        if hasattr(model.Translate, 'use_own_model') and model.Translate.use_own_model:
            model.Translate.model = create_translation_model(model)
        else:
            model.Translate.model = None

    def localize_field(self, field_name, instance):
        if instance.Translate.model:
            if not hasattr(instance, '_cached_translation'):
                try:
                    lang = self.get_language()
                except KeyError:
                    return self.translate(getattr(instance, field_name))
                try:
                    instance._cached_translation = instance.Translate.model.objects.get(obj=instance, lang=lang)
                except instance.Translate.model.DoesNotExist:
                    instance._cached_translation = None
            if instance._cached_translation:
                translation = getattr(instance._cached_translation, 'field_' + field_name)
                if translation:
                    return translation
        return self.translate(getattr(instance, field_name))

    def get_translate_url(self, obj):
        return reverse('translate.translate_obj', args=(obj._meta.app_label, obj._meta.object_name.lower(), obj.pk))

    def get_objects(self):
        from translate.models import SimpleTranslation
        return SimpleTranslation.objects

    def initial_fetch(self):
        if self.last_translation is not None:
            return
        from translate.models import Language
        for i in Language.objects.all():
            self.by_lang[i.code] = Translation(i)
        self._update(self.get_objects().select_related())

    def _update(self, translations):
        self.last_translation = datetime.now()
        for i in translations.exclude(lang__active=False):
            if not self.by_lang.has_key(i.lang.code):
                self.by_lang[i.lang.code] = Translation(i.lang)
            self.by_lang[i.lang.code].append(i)

    def update_cache(self):
        """Update translations cache"""
        if self.last_translation is None:
            self.initial_fetch()
        else:
            self._update(self.get_objects().select_related())

    def get_translations(self, string):
        """Get all available translations for string"""
        self.initial_fetch()
        string = unicode(string)
        ret = set((string,))
        for lang in self.by_lang.keys():
            try:
                trans = self.get_translation(string, lang)
                ret.add(unicode(trans))
            except KeyError:
                pass
        return tuple(ret)

    def get_translation(self, string, lang=None):
        self.initial_fetch()
        if lang is None:
            lang = get_language()
        return self.by_lang[lang][unicode(string)]

    def translate(self, string, lang=None):
        if lang is None:
            lang = get_language()
        try:
            return unicode(self.get_translation(string, lang))
        except KeyError:
            return unicode(string)
            from django.utils.translation import trans_real
            return unicode(trans_real.translation(lang).gettext(string), 'utf8')

    def get_current_language(self):
        return get_language()

    def get_language(self, code=None):
        self.initial_fetch()
        if code is None:
            code = self.get_current_language()
        return self.by_lang[code].lang

translator = Translator()
translate = translator.translate
update_translations = translator.update_cache

def add_model(sender, **kwargs):
    if issubclass(sender, models.Model):
        translator.add_model(sender)
signals.class_prepared.connect(add_model)
